<?php
/**
 * Created by PhpStorm.
 * User: DaveTang
 * Date: 2017/12/18
 * Time: 15:56
 */

namespace App\Controller\Component;
use Cake\Controller\Component;
use Cake\I18n\Time;
use Cake\Utility\Security;

class JwtComponent extends Component
{

    protected $_defaultConfig = [
        'allowedActions' => '*',
        'allowedRefresh' => '-'
    ];

    protected $_payload = [];
    protected $_signature;
    protected $_header = [];

    public function initialize(array $config)
    {
        $controller = $this->_registry->getController();
        $auth = $controller->request->getEnv('HTTP_AUTHORIZATION');
//        $auth = 'Bearer '.'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJpbmthbmthbi5jb20iLCJleHAiOiIxNTEzNjU0ODU5IiwiaWF0IjoiMTUxMzY1MTI1OSIsInVpZCI6MSwiZ2lkIjoyLCJ1c2VybmFtZSI6ImRhdmUifQ.QJXecGgRMNx4wKbGuxzYhsufPFeOdAwd2IX0xL61AG4';
        if (!empty($auth))
        {
            $auth = substr($auth, 7);
            $jwt = explode('.', $auth);
            if (count($jwt) === 3) {
                $this->_header = $jwt[0];
                $this->_payload = $jwt[1];
                $this->_signature = $jwt[2];
            }
        }

    }

    /**
     * 创建一个JWT
     *
     * @param string         $exp     JWT过期时间，以当前时间开始计算
     * @param string         $typ     JWT的类型，0为普通JWT，1为刷新用的JWT
     * @param Array          $data    向JWT中添加的额外数据
     *
     * @return JWT
     *
     */
    public function create(array $data = array(), $exp = '24 hours', $typ = 0)
    {
        $header = array(
            'typ'=>'JWT',
            'alg'=>'HS256'
        );

        $now = Time::now();
        $exp = $now->modify('+'.$exp)->toUnixString();

        $payload = array(
            'iss' => 'inkankan.com',
            'exp' => $exp,
            'typ' => $typ
        );

        $payload = array_merge($payload, $data);

        $segments = array();
        $segments[] = $this->urlsafeB64Encode(json_encode($header));
        $segments[] = $this->urlsafeB64Encode(json_encode($payload));
        $signing_input = implode('.', $segments);
        $signature = hash_hmac('sha256', $signing_input, Security::getSalt(), true);

        $segments[] = $this->urlsafeB64Encode($signature);
        return implode('.', $segments);
    }

    /**
     * 验证JWT是否正确
     *
     * @return True | Error Msg
     *
     */
    public function check()
    {
        $allowedActions = $this->getConfig('allowedActions');
        $allowedRefresh = $this->getConfig('allowedRefresh');

        if (!is_array($allowedActions) && $allowedActions === '*')
        {
            return true;
        }

        $controller = $this->_registry->getController();
        $ctr = $controller->request->getParam('controller');
        $act = $controller->request->getParam('action');

        if (array_key_exists($ctr, $allowedActions))
        {
            if (array_search($act, $allowedActions[$ctr]) !== false)
            {
                return true;
            }
        }

        if (empty($this->_signature))
        {
            $this->_error('auth_empty');
        }

        $hash = hash_hmac('sha256', $this->_header . '.' . $this->_payload, Security::getSalt(), true);
        if (!hash_equals($this->urlsafeB64Decode($this->_signature), $hash))
        {
            $this->_error('auth_error');
        }

        $exp = $this->get('exp');
        $typ = $this->get('typ');
        if(time() > $exp)
        {
            if ($typ != 0 ) {
                $this->_error('auth_error');
            } else {
                $this->_error('auth_timeout');
            }
        }

        if($typ != 0){
            if(array($ctr => $act) != $allowedRefresh)
            {
                $this->_error('auth_refresh_error');
            }
        }

        return true;
    }

    /**
     * 获取JWT中的一个值
     *
     * @param string         $key     值对应的$key
     *
     * @return key对应的值
     *
     */
    public function get($key){
        if(empty($this->_payload)){
            return false;
        }
        $payload = json_decode($this->urlsafeB64Decode($this->_payload), true);
        return $payload[$key] ?? null;
    }

    /**
     * 验证JWT是否正确
     *
     * @return True | Error Msg
     *
     */
    public function checkByArg($JWT = null)
    {
        $tmp = explode('.', $JWT);
        if (count($tmp) === 3) {
            $jwtArr = [
                'head' => $tmp[0],
                'payload' => $tmp[1],
                'signature' => $tmp[2]
            ];
        } else {
            return false;
        }

        $hash = hash_hmac('sha256', $jwtArr['head'] . '.' . $jwtArr['payload'], Security::getSalt(), true);
        if (!hash_equals($this->urlsafeB64Decode($jwtArr['signature']), $hash))
        {
            return false;
        }

        $exp = $this->getByArg($JWT, 'exp');
        $typ = $this->getByArg($JWT, 'typ');
        if(time() > $exp)
        {
            if ($typ != 0 ) {
                return false;
            } else {
                return false;
            }
        }
        return true;
    }

    /**
     * 获取JWT中的一个值
     *
     * @param string         $key     值对应的$key
     *
     * @return key对应的值
     *
     */
    public function getByArg($JWT, $key){
        $tmp = explode('.', $JWT);
        if (count($tmp) === 3) {
            $payload = $tmp[1];
        } else {
            return false;
        }
        $payload = json_decode($this->urlsafeB64Decode($payload), true);
        return $payload[$key] ?? null;
    }

    private function urlsafeB64Encode($input)
    {
        return str_replace('=', '', strtr(base64_encode($input), '+/', '-_'));
    }

    private function urlsafeB64Decode($input)
    {
        $remainder = strlen($input) % 4;
        if ($remainder) {
            $padlen = 4 - $remainder;
            $input .= str_repeat('=', $padlen);
        }
        return base64_decode(strtr($input, '-_', '+/'));
    }

    private function _error($msg) {
        $re = [
            'status' => 400,
            'msg' => $msg,
            'memo' => null
        ];
        echo json_encode($re);
        exit;
    }
}